package gek.uni.aspirant.prolog.utils.pl2;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import jpl.*;

import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.uml2.uml.Classifier;

// for DOM:
import java.io.*;

import org.w3c.dom.*;

import javax.xml.parsers.*;

import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;

public class PrologSystemUtils {

	  /** 
	   * @param 	strings 	текстовые данные для записи в файл,
	   * 						ориентировочно факты и правила на прологе.
	   * 			fileName	абсолютное имя файла на диске, куда будет
	   * 						осуществляться запись данных.
	   * 
	   * @return				переменная типа File.
	   * 
	   */
	  public static File writeStringToFile(Collection<String> strings,
	      String fileName) {
	    String newStr = "\r\n";
	    File file = new File(fileName);

	    try {
	      file.createNewFile();
	      System.out.println("File " + file.getName() + " (path = ["
	          + file.getPath() + "] ) was created!");
	    } catch (IOException e) {
	      System.err.println(e);
	    }

	    try {
	      FileWriter fw = new FileWriter(file);
	      Iterator<String> iter = strings.iterator();
	      while (iter.hasNext()) {
	        fw.write(iter.next() + newStr);
	      }
	      fw.close();
	      System.out.println("Everything was written to file " + file.getName());
	    } catch (IOException e) {
	      System.err.println("file error: " + e);
	    }
	    return file;
	  }

	  /** 
	   * @param 	shortFileName - имя файла на диске, без пути.
	   *  
	   * @return 	абсолютный путь к этому файлу на диске.
	   * 
	   */
	  public static String getFullFilePath(String shortFileName) {
	    String filePath = "";
	    File file;
	    URL url = PrologSystemUtils.class.getClassLoader().getResource(
	        shortFileName);
	    try {
	      file = new File(url.toURI());
	      filePath = file.getAbsolutePath();
	      filePath = filePath.replace("\\", "/");
	    } catch (URISyntaxException e) {
	      System.err.println("file error: " + e);
	    }
	    return filePath;
	  }
	  
	  /** 
	   * @param 	libPath - абсолютный путь к файлу библиотеки на диске.
	   *  
	   * @return 	true, если операция <consult(...)> завершилась успешно, 
	   * 			false - иначе.
	   * 
	   */
	  public static boolean consultData (String libPath) {
		  boolean consultSucceeded=false;
		  libPath = libPath.replace("\\", "/");
		  String consultPath = "consult('" + libPath + "').";	  
		  Query q = new Query(consultPath);
		  consultSucceeded = q.hasSolution();
		  q.close();
		  return consultSucceeded;  
	  }
	  
	  /**
	   * @param	query			запрос к прологу.
	   * 
	   * @param variablesMap 	результат преобразования ocl-выражения в пролог-код. 
	   * 						содержит мапу соответствий имя_ocl->имя_в_пролог и код 
	   * 						запроса на прологе.
	   * @return				возвращает результат выполнения запроса.
	   * 
	   */
	  
	  public static Hashtable[] exequteQuery (String query, 
			  Map<String,OCLExpression> variablesMap ) 
	  {
		  int solutionsReceived = 0;
		  int wantedNumOfSolutions = 2;
		  Hashtable[] solutions = new Hashtable[wantedNumOfSolutions];
		  Query q = new Query(query);	      
		  System.out.println("query = " + query);
		  System.out.println("variable map = " + variablesMap.toString());
	      System.out.println("results (hashtable): ");	      
	      while ((q.hasMoreSolutions())&&(solutionsReceived < wantedNumOfSolutions)) 
	      {
	    	  solutions[solutionsReceived] = q.nextSolution(); 
	    	  System.out.println("solution " + (solutionsReceived+1) + " = " + 
	    			  solutions[solutionsReceived].toString());
	    	  solutionsReceived++;
	      }
	      Hashtable[] resultSolutions = new Hashtable[solutionsReceived];
	      if (solutionsReceived == 2) {
	    	  resultSolutions[0]=solutions[1];
	    	  resultSolutions[1]=solutions[0];
	      } else if (solutionsReceived == 1) {
	    	  resultSolutions[0]=solutions[0];
	      }
	      q.close();
		  return resultSolutions;
	  }
	  
	  public static void parsePrologSolution (Hashtable solution, 
			  Map<String,OCLExpression> variablesMap ) 
	  {		 
		  System.out.println("parsing (solution):");
		  System.out.println("solution = " + solution.toString());
		  
		  Document doc = PrologSystemUtils.createEmptyDomXml();
		  Element root = doc.createElement("objectDiagram");
		  doc.appendChild(root);
		  
		  for (Entry<String, OCLExpression> entry : variablesMap.entrySet()) {
			  String propertyPrologName = entry.getKey();			  
			  if (solution.containsKey(propertyPrologName)) 
			  {
				  OCLExpression<Classifier> propertyOCL = 
					  (OCLExpression<Classifier>)entry.getValue();
				  String type = propertyOCL.getType().getName();
				  String propertyJavaName = entry.getValue().toString();				  
				  boolean isCollection=false;
				  String typeOfElements="", value="";
				  if (type.startsWith("Set")) {
					  isCollection = true;					  
					  typeOfElements = type.substring((type.indexOf('(')+1), 
							  type.indexOf(')'));
					  type = type.substring(0, type.indexOf('('));
				  }
				  value = PrologSystemUtils.convertPrologSolutionToString((Term)solution.get(propertyPrologName), false, doc, root, typeOfElements);
				  
				  String prefixPropertyPrologName=propertyJavaName;
				  String postfixPropertyPrologName=propertyJavaName;
				  int firstDotIndex=propertyJavaName.indexOf('.');
				  if (firstDotIndex>=0) {
					  prefixPropertyPrologName = propertyJavaName.substring(0,firstDotIndex);
				  	  postfixPropertyPrologName = propertyJavaName.substring(firstDotIndex+1);
				  }
				  
				  Element parent=null;
				  if (PrologSystemUtils.hasChildWithAttr(root, "name", prefixPropertyPrologName)) {
					  parent = PrologSystemUtils.getChildWithAttr(root, "name", prefixPropertyPrologName);
				  }
				  if (parent==null) {
					  parent = doc.createElement("object");
					  parent.setAttribute("name", prefixPropertyPrologName);
					  String parentType = "undefined";
					  String searchPrefix = prefixPropertyPrologName.substring(0,1).toUpperCase() + 
					  		prefixPropertyPrologName.substring(1);					
					  if (variablesMap.containsKey(searchPrefix))
						  parentType = ((OCLExpression<Classifier>)variablesMap.
								  get(searchPrefix)).getType().getName();
					  parent.setAttribute("type", parentType);
					  root.appendChild(parent);
				  }
				  
				  PrologSystemUtils.addElementToXml(doc, parent, postfixPropertyPrologName, 
						  type, value, isCollection, typeOfElements);			  
			  }
	      }
		  System.out.println("XML tree:");
		  System.out.println(PrologSystemUtils.convertXmlTreeToString(doc));
	  }
	  
	  public static void addElementToXml (Document doc, Element parent, 
			  String variable, String type, String value, boolean isCollection, String typeOfElements) 
	  {
		  String prefix="", postfix="";	  
		  boolean lastIteration = (variable.indexOf('.')<0);
		  if (lastIteration) {
			  prefix = variable;
		  } else {
			  int dotIndex = variable.indexOf('.');
			  prefix = variable.substring(0, dotIndex);
			  postfix = variable.substring(dotIndex+1);
		  }
          Element property = doc.createElement("property");
          parent.appendChild(property);          
          if (lastIteration) {
        	  property.setAttribute("name", prefix);  
        	  property.setAttribute("type", type);        	  
        	  if (isCollection) {
        		  property.setAttribute("valueType", typeOfElements);
        		  value = value+",";
        		  boolean valueIsNotEmpty = (value.indexOf(',')>=0);
        		  while (valueIsNotEmpty) {
        			  String valueElement = value.substring(0, value.indexOf(','));
        			  value = value.substring(value.indexOf(',')+1);
        			  valueIsNotEmpty = (value.indexOf(',')>=0);
        			  Element child = doc.createElement("child");
        			  if (valueElement.startsWith("_")) {
        				  child.setAttribute("objectRef", valueElement);
        			  } else {
        				  child.setAttribute("value", valueElement);
        			  }
        			  property.appendChild(child);
        		  }
        	  } else {
        		  property.setAttribute("value", value);
        	  }
          } else {
        	  addElementToXml(doc, property, postfix, type, value, isCollection, typeOfElements);
          }                                      		  		  		 		 
	  }
	  
	  public static boolean hasChildWithAttr (Element parent, String attrName, String attrValue) {
		  NodeList nodes = parent.getChildNodes();
		  for(int i=0; i<nodes.getLength(); i++){
		    Node node = nodes.item(i);
		    if(node instanceof Element){
		      Element childNode = (Element) node;
		      if (childNode.hasAttribute(attrName)) {
		    	  String attribute = childNode.getAttribute(attrName);
		    	  if (attribute.equalsIgnoreCase(attrValue)) 
		    		  return true;
		      }
		    }
		  }
		  return false;
	  }
	  
	  public static Element getChildWithAttr (Element parent, String attrName, String attrValue) {
		  NodeList nodes = parent.getChildNodes();
		  for(int i=0; i<nodes.getLength(); i++){
		    Node node = nodes.item(i);
		    if(node instanceof Element){
		      Element childNode = (Element) node;
		      if (childNode.hasAttribute(attrName)) {
		    	  String attribute = childNode.getAttribute(attrName);
		    	  if (attribute.equalsIgnoreCase(attrValue)) 
		    		  return childNode;
		      }
		    }
		  }
		  return null;
	  }
	  
	  public static String convertPrologSolutionToString (Term term, 
			  boolean createXmlObj, Document doc, Element parent, String valueType) 
	  {
		  if (term instanceof jpl.Atom) {
			  Atom atom_ = (Atom)term;
			  if (atom_.toString().equalsIgnoreCase("[]")) return "";
			  return atom_.toString();
		  }
		  if (term instanceof jpl.Variable) {
			  jpl.Variable var_ = (jpl.Variable)term;
			  if (createXmlObj) {
				  if (!PrologSystemUtils.hasChildWithAttr(parent, "name", var_.toString())) {
					  Element child = doc.createElement("object");
					  child.setAttribute("name", var_.toString());
					  child.setAttribute("type", valueType);
					  child.setAttribute("value", "NOT_NULL");
					  parent.appendChild(child);					  
				  }
			  }
			  return var_.toString();
		  }
		  if (term instanceof jpl.Integer) {
			  jpl.Integer int_ = (jpl.Integer)term;
			  return int_.toString();
		  }
		  if (term instanceof jpl.Float) {
			  jpl.Float float_ = (jpl.Float)term;
			  return float_.toString();
		  }
		  if (term instanceof jpl.Compound) {
			  jpl.Compound compound_ = (jpl.Compound)term;
			  StringBuilder bldr = new StringBuilder();
			  String result1 = PrologSystemUtils.convertPrologSolutionToString(compound_.arg(1), true, doc, parent, valueType);
			  String result2 = PrologSystemUtils.convertPrologSolutionToString(compound_.arg(2), true, doc, parent, valueType);
			  bldr.append(result1);
			  if (!result2.isEmpty())
				  bldr.append(",").append(result2);
			  return bldr.toString();			  
		  }		  		  
		  return "";
	  }
	  
	  public static Document createEmptyDomXml () {
          Document doc = null;
		  try {
        	  //Creating an empty XML Document
        	  DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
        	  DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
        	  doc = docBuilder.newDocument();
	      } catch (Exception e) {
	          System.out.println(e);
	      }
	      return doc;
	  }
	  
	  public static String convertXmlTreeToString (Document doc) {
		  String xmlString="";
          try {
        	  //Output the XML. set up a transformer
        	  TransformerFactory transfac = TransformerFactory.newInstance();
        	  Transformer trans = transfac.newTransformer();
        	  trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
        	  trans.setOutputProperty(OutputKeys.INDENT, "yes");

        	  //create string from xml tree
        	  StringWriter sw = new StringWriter();
        	  StreamResult result = new StreamResult(sw);
        	  DOMSource source = new DOMSource(doc);
        	  trans.transform(source, result);
        	  xmlString = sw.toString();
	      } catch (Exception e) {
	          System.out.println(e);
	      }
		  return xmlString;
	  }
}
